Step 35: HTTP Errors
We must update the all route handlers to return an HTTP error similar to the following:
router.get("/bookmarks", async (req, res) => {
try {
const { title, url } = req.query;
const bookmarks = await bookmarkDao.readAll({ title, url });
res.json({
status: 200,
message: `Successfully retrieved ${bookmarks.length} bookmarks!`,
data: bookmarks,
});
} catch (err) {
const code = err.status || 500
res.status(code).json({
status: code,
message: err.message || `Internal Server Error!`,
});
}
});
Additionally, update the route handlers to throw 404 errors when resource is not found. For example:
router.get("/bookmarks/:id", async (req, res) => {
try {
const { id } = req.params;
const bookmark = await bookmarkDao.read(id);
if (!bookmark) {
throw new ApiError(404, "Resource not found!")
}
res.json({
status: 200,
message: `Successfully retrieved the following bookmark!`,
data: bookmark,
});
} catch (err) {
const code = err.status || 500;
res.status(code).json({
status: code,
message: err.message || `Internal Server Error!`,
});
}
});
We should also update the tests/routes/bookmarks.test.js
file:
import { describe, it, expect, beforeEach, afterAll, beforeAll } from "vitest";
import app from "../../src/index.js";
import supertest from "supertest";
import { faker } from "@faker-js/faker";
import { bookmarkDao } from "../../src/routes/bookmarks.js";
import * as db from "../../src/data/db.js";
import * as dotenv from "dotenv";
import mongoose from "mongoose";
dotenv.config();
const request = new supertest(app);
describe("Test API /bookmarks endpoints", () => {
const numBookmarks = 5;
beforeAll(async () => {
db.connect(process.env.DB_TEST_URI);
await bookmarkDao.deleteAll();
});
beforeEach(async () => {
await bookmarkDao.deleteAll();
const cutoff = 2;
for (let index = 0; index < cutoff; index++) {
await bookmarkDao.create({
title: "Fake title",
url: `https://fake-url-${index}.com`,
});
}
for (let index = cutoff; index < numBookmarks; index++) {
await bookmarkDao.create({
title: faker.lorem.sentence(),
url: faker.internet.url(),
});
}
});
describe("GET bookmarks", () => {
it("GET all bookmarks", async () => {
const response = await request.get("/bookmarks");
expect(response.status).toBe(200);
expect(response.body.data.length).toBe(numBookmarks);
});
it("GET all bookmarks given title", async () => {
const title = "Fake title";
const response = await request.get(`/bookmarks?title=${title}`);
expect(response.status).toBe(200);
expect(response.body.data.length).toBe(2);
});
it("GET all bookmarks given URL", async () => {
const url = "https://fake-url-0.com";
const response = await request.get(`/bookmarks?url=${url}`);
expect(response.status).toBe(200);
expect(response.body.data.length).toBe(1);
});
});
describe("POST a bookmark", () => {
it("Valid title and URL", async () => {
const title = faker.lorem.sentence();
const url = faker.internet.url();
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(201);
expect(response.body.data._id).toBeDefined();
expect(response.body.data.title).toBe(title);
expect(response.body.data.url).toBe(url);
});
it("Empty title", async () => {
const title = "";
const url = faker.internet.url();
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
it("Null title", async () => {
const title = null;
const url = faker.internet.url();
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
it("Undefined title", async () => {
const title = undefined;
const url = faker.internet.url();
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
it("Empty url", async () => {
const title = faker.lorem.sentence();
const url = "";
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
it("Null url", async () => {
const title = faker.lorem.sentence();
const url = null;
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
it("undefined url", async () => {
const title = faker.lorem.sentence();
const url = undefined;
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
it("invalid url", async () => {
const title = faker.lorem.sentence();
const url = faker.lorem.sentence();
const response = await request.post("/bookmarks").send({
title,
url,
});
expect(response.status).toBe(400);
});
});
describe("GET a bookmark given its ID", () => {
it("Valid ID", async () => {
const index = Math.floor(Math.random() * numBookmarks);
const bookmarks = await bookmarkDao.readAll({});
const bookmark = bookmarks[index];
const response = await request.get(`/bookmarks/${bookmark.id}`);
expect(response.status).toBe(200);
expect(response.body.data._id).toBe(bookmark.id);
expect(response.body.data.title).toBe(bookmark.title);
expect(response.body.data.url).toBe(bookmark.url);
});
it("Invalid ID", async () => {
const response = await request.get(
`/bookmarks/${mongoose.Types.ObjectId().toString()}`
);
expect(response.status).toBe(404);
});
});
describe("Update a bookmark given its ID", () => {
it("Valid ID", async () => {
const index = Math.floor(Math.random() * numBookmarks);
const bookmarks = await bookmarkDao.readAll({});
const bookmark = bookmarks[index];
const response = await request.put(`/bookmarks/${bookmark.id}`).send({
title: "Update title",
url: "https://update-url.com",
});
expect(response.status).toBe(200);
expect(response.body.data._id).toBe(bookmark.id);
expect(response.body.data.title).toBe("Update title");
expect(response.body.data.url).toBe("https://update-url.com");
});
it("Invalid ID", async () => {
const response = await request
.put(`/bookmarks/${mongoose.Types.ObjectId().toString()}`)
.send({
title: "Update title",
url: "https://update-url.com",
});
expect(response.status).toBe(404);
});
});
describe("Delete a bookmark given its ID", () => {
it("Valid ID", async () => {
const index = Math.floor(Math.random() * numBookmarks);
const bookmarks = await bookmarkDao.readAll({});
const bookmark = bookmarks[index];
const response = await request.delete(`/bookmarks/${bookmark.id}`);
expect(response.status).toBe(200);
expect(response.body.data._id).toBe(bookmark.id);
expect(response.body.data.title).toBe(bookmark.title);
expect(response.body.data.url).toBe(bookmark.url);
});
it("Invalid ID", async () => {
const response = await request.delete(
`/bookmarks/${mongoose.Types.ObjectId().toString()}`
);
expect(response.status).toBe(404);
});
});
afterAll(async () => {
await bookmarkDao.deleteAll();
});
});
Run all the tests and make sure they all pass. Then, save and commit changes.